﻿using System;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace Nomo.UnityCoreModule
{
    /// <summary>
    /// 连续得分奖励管理器。
    /// </summary>
    public sealed class ComboManager : SingletonBehaviour<ComboManager>
    {
        /// <summary>
        /// 连续得分模型。
        /// </summary>
        public readonly struct ComboModel : IComparable<ComboModel>, IEquatable<ComboModel>
        {
            /// <summary>
            /// 门槛。
            /// </summary>
            public readonly int Threshold;

            /// <summary>
            /// 持续时间。
            /// </summary>
            public readonly float Duration;

            /// <summary>
            /// 分数因子。
            /// </summary>
            public readonly float ScoreMultiplier;

            /// <summary>
            /// 默认的连续得分模型。
            /// </summary>
            public static readonly ComboModel Default = new ComboModel(0, float.PositiveInfinity, 1F);

            /// <summary>
            /// 初始化 <see cref="ComboModel"/> 结构的新值。
            /// </summary>
            /// <param name="threshold">门槛。</param>
            /// <param name="duration">持续时间。</param>
            /// <param name="scoreMultiplier">分数因子。</param>
            public ComboModel(int threshold, float duration, float scoreMultiplier)
            {
                if (threshold < 0)
                {
                    throw new ArgumentOutOfRangeException(nameof(threshold));
                }
                if (duration < 0F || float.IsNaN(duration))
                {
                    throw new ArgumentOutOfRangeException(nameof(duration));
                }
                if (float.IsInfinity(scoreMultiplier) || float.IsNaN(scoreMultiplier))
                {
                    throw new ArgumentOutOfRangeException(nameof(scoreMultiplier));
                }
                Threshold       = threshold;
                Duration        = duration;
                ScoreMultiplier = scoreMultiplier;
            }

            int IComparable<ComboModel>.CompareTo(ComboModel other)
            {
                return Threshold - other.Threshold;
            }

            bool IEquatable<ComboModel>.Equals(ComboModel other)
            {
                return Threshold == other.Threshold;
            }

            /// <inheritdoc />
            public override bool Equals(object obj)
            {
                return obj is ComboModel other && ((IEquatable<ComboModel>) this).Equals(other);
            }

            /// <inheritdoc />
            public override int GetHashCode()
            {
                return Threshold;
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="a"></param>
            /// <param name="b"></param>
            /// <returns></returns>
            public static bool operator ==(ComboModel a, ComboModel b)
            {
                return a.Threshold == b.Threshold;
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="a"></param>
            /// <param name="b"></param>
            /// <returns></returns>
            public static bool operator !=(ComboModel a, ComboModel b)
            {
                return a.Threshold != b.Threshold;
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="a"></param>
            /// <param name="b"></param>
            /// <returns></returns>
            public static bool operator <(ComboModel a, ComboModel b)
            {
                return a.Threshold < b.Threshold;
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="a"></param>
            /// <param name="b"></param>
            /// <returns></returns>
            public static bool operator >(ComboModel a, ComboModel b)
            {
                return a.Threshold > b.Threshold;
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="a"></param>
            /// <param name="b"></param>
            /// <returns></returns>
            public static bool operator <=(ComboModel a, ComboModel b)
            {
                return a.Threshold <= b.Threshold;
            }

            /// <summary>
            /// 
            /// </summary>
            /// <param name="a"></param>
            /// <param name="b"></param>
            /// <returns></returns>
            public static bool operator >=(ComboModel a, ComboModel b)
            {
                return a.Threshold >= b.Threshold;
            }
        }

        private ComboModel? _defaultComboModel;

        public ComboModel DefaultComboModel
        {
            get => GetDefaultComboModel();
            set
            {
                if (value.Threshold == ComboModel.Default.Threshold && value.Duration == ComboModel.Default.Duration)
                {
                    _defaultComboModel = value;
                }
            }
        }

        private ComboModel GetDefaultComboModel()
        {
            if (_defaultComboModel.HasValue)
            {
                return _defaultComboModel.Value;
            }

            // try
            // {
            //     var comboModel = _comboModels[0];
            //     if (comboModel.Threshold == ComboModel.Default.Threshold && comboModel.Duration == ComboModel.Default.Duration)
            //     {
            //         return comboModel;
            //     }
            //     return ComboModel.Default;
            // }
            // catch
            // {
            //     return ComboModel.Default;
            // }

            if (_comboModels != null && _comboModels.Length > 0)
            {
                var firstComboModel = _comboModels[0];
                if (firstComboModel.Threshold == ComboModel.Default.Threshold && firstComboModel.Duration == ComboModel.Default.Duration)
                {
                    return firstComboModel;
                }
            }

            return ComboModel.Default;
        }

        [NonSerialized] public bool         Timing;
        private                ComboModel[] _comboModels;
        private                float        _timer;
        private                uint         _comboLevel;
        private                uint         _comboTimes;
        private                uint         _pointCount;
        private                uint         _activePointCount;

        public event Action<int, int> PointCountChanged;
        public event Action<float>    ScoreMultiplierChanged;

        public float ScoreMultiplier
        {
            get
            {
                try
                {
                    return _comboModels[_comboLevel].ScoreMultiplier;
                }
                catch
                {
                    return _defaultComboModel?.ScoreMultiplier ?? ComboModel.Default.ScoreMultiplier;
                }
            }
        }

        /// <summary>
        /// 使用连续得分模型的序列初始化。
        /// </summary>
        /// <param name="comboModels">成员为连续得分模型的序列。</param>
        public void Initialize(IEnumerable<ComboModel> comboModels)
        {
            if (comboModels == null)
            {
                comboModels = new ComboModel[0];
            }

            var comboModelsArray = comboModels.Distinct().OrderBy(e => e).ToArray();
            if (comboModelsArray.All(e => e != ComboModel.Default))
            {
                comboModelsArray = comboModelsArray.Prepend(ComboModel.Default).ToArray();
            }

            _defaultComboModel = _comboModels.First();
            _comboModels       = comboModelsArray;
            Init();
        }

        /// <summary>
        /// 初始化。
        /// </summary>
        public void Init()
        {
            var defaultComboModel = GetDefaultComboModel();
            _defaultComboModel = defaultComboModel;
            _timer             = defaultComboModel.Duration;
            _comboLevel        = default;
            _comboTimes        = default;
        }

        private void Update()
        {
            if (!Timing)
            {
                return;
            }

            if (_comboTimes == 0U)
            {
                return;
            }

            _timer -= Time.deltaTime;

            if (_timer <= 0F) { }
        }
    }
}
